﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.IO;
using System.Xml.Xsl;
using SaxonWrapper;
using System.Xml.Schema;

namespace ProcessingCatalogue
{
    public class Program
    {
        private static Random random = new Random();

        /// <summary>
        /// 2. Write program that extracts all different artists which are found in the catalog.xml. 
        /// For each author you should print the number of albums in the catalogue. 
        /// Use the DOM parser and a hash-table.
        /// </summary>
        public static void DifferentArtistsNumberAlbumsDom(string fileName)
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(fileName);
            Dictionary<string, int> artists = new Dictionary<string, int>();
            foreach (XmlNode album in xmlDoc.DocumentElement.ChildNodes)
            {
                if (album.Name == "album")
                {
                    XmlNode artist = album["artist"];
                    if (artist != null && artist.InnerText != null)
                    {
                        if (!artists.ContainsKey(artist.InnerText))
                        {
                            artists.Add(artist.InnerText, 0);
                        }
                        artists[artist.InnerText]++;
                    }
                }
            }
            foreach (var pair in artists)
            {
                Console.WriteLine("{0}: {1}", pair.Key, pair.Value);
            }
        }

        /// <summary>
        /// 3. Write program that extracts all different artists which are found in the catalog.xml. 
        /// For each author you should print the number of albums in the catalogue. 
        /// Implement using XPath.
        /// </summary>
        /// <param name="filename"></param>
        public static void DifferentArtistsNumberAlbumsXpath(string fileName)
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(fileName);
            string xPathQuery = "/catalogue/album/artist";
            Dictionary<string, int> artists = new Dictionary<string, int>();
            XmlNodeList artistsList = xmlDoc.SelectNodes(xPathQuery);
            foreach (XmlNode artist in artistsList)
            {
                if (artist != null && artist.InnerText != null)
                {
                    if (!artists.ContainsKey(artist.InnerText))
                    {
                        artists.Add(artist.InnerText, 0);
                    }
                    artists[artist.InnerText]++;
                }
            }
            foreach (var pair in artists)
            {
                Console.WriteLine("{0}: {1}", pair.Key, pair.Value);
            }
        }

        /// <summary>
        /// 4. Using the DOM parser write a program to delete from catalog.xml all albums, with price > 20$.
        /// </summary>
        /// <param name="filename"></param>
        public static void DeleteAlbumsOver20Dollars(string fileName)
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(fileName);
            foreach (XmlNode album in xmlDoc.DocumentElement.ChildNodes)
            {
                if (album.Name == "album")
                {
                    XmlNode priceNode = album["price"];
                    if (priceNode != null && priceNode.InnerText != null)
                    {
                        decimal price = decimal.Parse(priceNode.InnerText);
                        if (price > 20)
                        {
                            xmlDoc.DocumentElement.RemoveChild(album);
                        }
                    }
                }
            }
            xmlDoc.Save(fileName);
        }

        /// <summary>
        /// 5. Write a program, which using XmlReader extracts all song titles from catalog.xml.
        /// </summary>
        /// <param name="filename"></param>
        public static void ExtractSongTitlesDom(string fileName)
        {
            using (XmlReader reader = XmlReader.Create(fileName))
            {
                Console.WriteLine("Song titles:");
                while (reader.ReadToFollowing("title"))
                {
                    Console.WriteLine(reader.ReadElementString());
                }
            }
        }

        /// <summary>
        /// 6. Write a program, which using XDocument extracts all song titles from catalog.xml.
        /// </summary>
        /// <param name="filename"></param>
        public static void ExtractSongTitlesXDoc(string fileName)
        {
            XDocument xmlDoc = XDocument.Load(fileName);
            Console.WriteLine("Song titles:");
            xmlDoc.Descendants("title").ToList().
                ForEach(t => Console.WriteLine(t.Value));
        }

        /// <summary>
        /// 7. In a text file in already known format are stored the names, 
        /// address and phone number of given person. Write a program, 
        /// which (using the DOM parser) creates new XML document, 
        /// which contains these data in structured view.
        /// </summary>
        /// <param name="filename"></param>
        public static void CreatePersons(string fileName)
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.AppendChild(
                xmlDoc.CreateXmlDeclaration("1.0", Encoding.UTF8.BodyName, string.Empty));
            XmlElement xmlRoot = xmlDoc.CreateElement("persons");
            xmlDoc.AppendChild(xmlRoot);
            for (int i = 1; i <= 5; i++)
            {
                XmlElement person = xmlDoc.CreateElement("person");

                XmlElement name = xmlDoc.CreateElement("name");
                name.InnerText = "Pesho" + i;
                person.AppendChild(name);

                XmlElement address = xmlDoc.CreateElement("address");
                address.InnerText = "Sofia " + random.Next(1000, 2000);
                person.AppendChild(address);

                XmlElement phone = xmlDoc.CreateElement("phone");
                phone.InnerText = "088" + random.Next(2, 10) + " " +
                    random.Next(100, 1000) + " " + random.Next(100, 1000);
                person.AppendChild(phone);

                xmlRoot.AppendChild(person);
            }

            xmlDoc.Save(fileName);
        }

        /// <summary>
        /// 8. Write a program, which (using XmlReader and XmlWriter) reads the file 
        /// catalog.xml and creates the file album.xml, in which stores in appropriate 
        /// way  the names of all albums and their authors.
        /// </summary>
        /// <param name="fileName"></param>
        public static void ExtractAlbums(string inputFileName, string outputFileName)
        {
            using (XmlReader reader = XmlReader.Create(inputFileName))
            using (XmlTextWriter writer = new XmlTextWriter(outputFileName, Encoding.UTF8))
            {
                writer.Formatting = Formatting.Indented;
                writer.IndentChar = '\t';
                writer.Indentation = 1;

                writer.WriteStartDocument();
                writer.WriteStartElement("albums");
                while (reader.ReadToFollowing("album"))
                {
                    writer.WriteStartElement("album");
                    for (int i = 0; i < 2; i++)
                    {
                        while (reader.Name != "name" && reader.Name != "artist")
                        {
                            reader.Read();
                        }
                        writer.WriteElementString(reader.Name, reader.ReadElementString());
                    }
                    writer.WriteEndElement();
                }
                writer.WriteEndElement();
                writer.WriteEndDocument();
            }
        }

        /// <summary>
        /// 9. Write a program to traverse given directory and write to a XML file 
        /// its contents together with all subdirectories and files. Use tags 
        /// <file> and <dir> with appropriate attributes. For the generation of 
        /// the XML document use the class XmlWriter.
        /// </summary>
        public static void TraverseDirectoriesXmlWriter(string dir, string outputFile)
        {
            using (XmlTextWriter writer = new XmlTextWriter(outputFile, Encoding.UTF8))
            {
                writer.Formatting = Formatting.Indented;
                writer.IndentChar = '\t';
                writer.Indentation = 1;

                writer.WriteStartDocument();
                TraverseDirectoryXmlWriter(new DirectoryInfo(dir), writer);
                writer.WriteEndDocument();
            }
        }

        private static void TraverseDirectoryXmlWriter(DirectoryInfo directory, XmlTextWriter writer)
        {
            writer.WriteStartElement("dir");
            writer.WriteStartAttribute("name");
            writer.WriteValue(directory.Name);
            writer.WriteEndAttribute();
            foreach (DirectoryInfo dir in directory.GetDirectories())
            {
                TraverseDirectoryXmlWriter(dir, writer);
            }
            foreach (FileInfo currentFile in directory.GetFiles())
            {
                writer.WriteStartElement("file");
                writer.WriteStartAttribute("name");
                writer.WriteValue(currentFile.Name);
                writer.WriteEndAttribute();
                writer.WriteEndElement();
            }
            writer.WriteEndElement();
        }

        /// <summary>
        /// 10. Write a program to traverse given directory and write to a XML file 
        /// its contents together with all subdirectories and files. Use tags 
        /// <file> and <dir> with appropriate attributes. 
        /// Use XDocument, XElement and XAttribute.
        /// </summary>
        public static void TraverseDirectoriesLINQ(string dir, string outputFile)
        {
            DirectoryInfo directory = new DirectoryInfo(dir);
            XElement element = new XElement("dir");
            TraverseDirectoryLINQ(element, directory);

            XDocument xdoc = new XDocument();
            xdoc.Add(element);
            xdoc.Save(outputFile);
        }

        private static void TraverseDirectoryLINQ(XElement element, DirectoryInfo directory)
        {
            element.Add(new XAttribute("Name", directory.Name));
            foreach (var subDir in directory.GetDirectories())
            {
                XElement subDirElement = new XElement("dir");
                element.Add(subDirElement);
                TraverseDirectoryLINQ(subDirElement, subDir);
            }
            foreach (var file in directory.GetFiles())
            {
                XElement fileElement = new XElement("file");
                fileElement.Add(new XAttribute("Name", file.Name));
                element.Add(fileElement);
            }
        }

        /// <summary>
        /// 11. Write a program, which extract from the file catalog.xml 
        /// the prices for all albums, published before 5 years or more. 
        /// Use XPath query. 
        /// </summary>
        public static void FindAlbumsXPath(string fileName, int years)
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(fileName);
            string xPathQuery = "/catalogue/album";
            XmlNodeList albumsList = xmlDoc.SelectNodes(xPathQuery);
            foreach (XmlNode album in albumsList)
            {
                if (album != null && DateTime.Now.Year - 
                    int.Parse(album["year"].InnerText) > years)
                {
                    Console.WriteLine(album["price"].InnerText);
                }
            }
        }

        /// <summary>
        /// 11. Write a program, which extract from the file catalog.xml 
        /// the prices for all albums, published before 5 years or more. 
        /// Use XPath query. 
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="years"></param>
        public static void FindAlbumsLINQ(string fileName, int years)
        {
            XDocument document = XDocument.Load(fileName);
            var albums = document.Descendants("album");
            foreach (var album in albums)
            {
                XElement year = album.Descendants("year").First();
                if (DateTime.Now.Year - int.Parse(year.Value) > years)
                {
                    XElement price = album.Descendants("price").First();
                    Console.WriteLine(price.Value);
                }
            }
        }

        /// <summary>
        /// 12. Create an XSL stylesheet, which transforms the file catalog.xml 
        /// into HTML document, formatted for viewing in a standard Web-browser.
        /// 13. Write a C# program to apply the XSLT stylesheet transformation 
        /// on the file catalog.xml using the class XslTransform.
        /// </summary>
        public static void XslConvert(string cataloguePath, string xslPath, string outputHtmlPath)
        {
            XslCompiledTransform xslt = new XslCompiledTransform();
            xslt.Load(xslPath);
            xslt.Transform(cataloguePath, outputHtmlPath);
        }

        /// <summary>
        /// 14. Read some tutorial about the XQuery language. Implement the 
        /// XML to HTML transformation with XQuery (instead of XSLT). 
        /// Download some open source XQuery library for .NET and execute 
        /// the XQuery to transform the catalog.xml to HTML.
        /// </summary>
        public static void TransformXmlToHtmlXQuery(string xmlFilePath, 
            string xqFilePath, string htmlOutputFilePath)
        {
            using (StreamWriter streamWriter = new StreamWriter(htmlOutputFilePath,
                false, Encoding.UTF8))
            {
                // Load the input doc
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.Load(xmlFilePath);

                // Create the output XmlWriter
                XmlTextWriter xmlWriter = new XmlTextWriter(streamWriter);

                // Do the transformation
                XQueryProcessor xp = new XQueryProcessor();
                xp.LoadFromFile(xqFilePath);
                xp.RunQuery(xmlDoc, xmlWriter);

            }
        }

        /// <summary>
        /// 15. Using Visual Studio generate an XSD schema for the file catalog.xml. 
        /// Write a C# program that takes an XML file and an XSD file (schema) and 
        /// validates the XML file against the schema. Test it with valid XML catalogs 
        /// and invalid XML catalogs.
        /// </summary>
        public static bool ValidateXmlDocument(string cataloguePath, string xsdPath)
        {
            bool result = true;

            XmlReaderSettings settings = new XmlReaderSettings();
            settings.ValidationType = ValidationType.Schema;
            settings.Schemas.Add(null, xsdPath);
            settings.ValidationEventHandler +=
                (sender, args) => { Console.WriteLine(args.Message); result = false; };

            using (XmlReader reader = XmlReader.Create(cataloguePath, settings))
            {
                while (reader.Read()) { }
            }

            Console.WriteLine("Validation completed.");
            return result;
        }

        public static void Main()
        {
            ValidateXmlDocument(@"..\..\Catalogue.xml", @"..\..\Catalogue.xsd");
        }
    }
}
